package nl.altenpts.net.kad;

import java.io.Serializable;
import java.util.Random;

public class Id implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public Id(byte[] id) {
		this.id = id;
	}
	
	public int getMatchingPrefix(Id other) {
		assert(size() == other.size());
		int bytes = 0, bits = 0;
		
		for(int i = 0; i < id.length; i++) {
			if(id[i] == other.id[i]) {
				bytes++;				
			} else {
				break;
			}
		}
		
		byte byte1, byte2;
		int bit1, bit2;
		
		byte1 = id[bytes];
		byte2 = other.id[bytes];
		
		for(int i = 0; i < 8; i++) {
			
			bit1 = byte1 & (1 << (7 - i));
			bit2 = byte2 & (1 << (7 - i));
			
			if(bit1 == bit2) {
				bits++;
			} else {
				break;
			}			
		}
		
		return bytes * 8 + bits;
	}
	
	public Id getInverse() {
		byte[] invid = new byte[id.length];
		
		for(int i = 0; i < invid.length; i++) {
			invid[i] = (byte) ~id[i];
		}
		
		return new Id(invid);
	}
	
	public boolean matchesPrefix(Id other, int prefixlength) {		
		assert(size() == other.size());
		int nbytes = prefixlength / 8;
		int nbits = prefixlength % 8;
		
		for(int i = 0; i < nbytes; i++) {
			if(other.id[i] != id[i]) {
				return false;
			}
		}
		
		byte byte1, byte2;
		
		byte1 = id[nbytes];
		byte2 = other.id[nbytes];
		
		for(int i = 0; i < nbits; i++) {
			int bit1, bit2;
			
			bit1 = byte1 & (1 << (7 - i));
			bit2 = byte2 & (1 << (7 - i));
			
			if(bit1 != bit2) {
				return false;
			}			
		}		
		return true;
	}

	/**
	 * test
	 * 
	 * @return The size of the Id in bits
	 */
	public int size() {
		return id.length * 8;
	}
	
	public int getBit(int i) {
		assert(i >= 0 && i < size());
		
		int bytenum, bitnum;		
		
		bytenum = i / 8;
		bitnum  = i % 8;
		
		return getBit(id[bytenum], bitnum);
		
	}
	
	public static int getBit(byte b, int bit)
	{
		assert(bit > 0 && bit < 8);
		return Integer.signum(b & (1 << (7 - bit)));
	}
	
	public boolean matchesBit(Id other, int bit) {
		return getBit(bit) == other.getBit(bit);		
	}
	
	public byte[] getDistance(Id other) {
		
		assert(other.size() == size());
		
		byte[] distance = new byte[size() / 8];
		
		for(int i = 0; i < distance.length; i++) {
			distance[i] = (byte) (id[i] ^ other.id[i]);
		}
		return distance;
	}
	
	public boolean equals(Object o) {
		Id other = (Id) o;
		
		if(id.length != other.size()) {
			return false;
		}
		
		for(int i = 0; i < id.length; i++) {
			if(id[i] != other.id[i]) {
				return false;						
			}
		}
		return true;
	}
	
	public byte[] getByteArray() {
		return id;
	}
	
	public static Id getRandomId(Id root, int prefixlength, boolean notroot) {
		byte[] bid = new byte[root.size() / 8];
		byte[] broot = root.getByteArray();
		int i;
		
		new Random().nextBytes(bid);
		
		if(prefixlength < 0) {
			return new Id(bid);
		}
	
		for(i = 0; i < prefixlength / 8; i++) {
			bid[i] = broot[i];
		}
		
		if(prefixlength % 8 != 0) {
			byte rootbyte = broot[i];
			byte mask = (byte) ((1 << (8 - (prefixlength % 8))) - 1);
			
			bid[i] &= mask;
			mask = (byte) ~mask;			
			rootbyte &= mask;
			   
			bid[i] |= rootbyte;			
		}
		
		if(notroot) {
			/* set bit after inverse to root bit */
			int bit = root.getBit(prefixlength);
			byte mask = (byte) (1 << (8 - ((prefixlength % 8) + 1)));
			if(bit != 0) {
				mask = (byte) ~mask;
				bid[prefixlength / 8] &= mask;
			} else {
				bid[prefixlength / 8] |= mask;
			}
		}

		return new Id(bid);
	}
	
	private byte[] id;
}
